package com.happe.foodeasy.helper;

import com.happe.foodeasy.exception.TooManyAnnotationException;
import com.happe.foodeasy.relation.annotation.RelationalMarkAnnotation;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

/**
 * annotation helper class
 * Created by wubin on 2016-05-10.
 */
public class AnnotationHelper {

    /**
     * This method is used to retrieve all classes what are marked by target marked annotation,
     * the target classes are the same package with target marked annotation.
     *
     * @param targetMarkAnnotation the target marked annotation that the target classes should have.
     * @return the target classes what have been marked by target annotation.
     * @throws IOException
     * @throws ClassNotFoundException
     */
    public static Class<? extends Annotation>[] parseMarkAnnotation(Class<? extends Annotation> targetMarkAnnotation) throws IOException, ClassNotFoundException {
        return parseMarkAnnotationAndPackage(targetMarkAnnotation, targetMarkAnnotation.getPackage().getName());
    }

    /**
     * This method is used to retrieve all classes what are marked by target marked annotation,
     *
     * @param targetMarkAnnotation the target marked annotation that the target classes should have.
     * @param packageName          the target package name that wants to be checked.
     * @return the target classes what have been marked by target annotation, or return null if there are no target
     * class that been marked.
     * @throws ClassNotFoundException
     */
    public static Class<? extends Annotation>[] parseMarkAnnotationAndPackage(Class<? extends Annotation> targetMarkAnnotation, String packageName) throws ClassNotFoundException {

        Class[] clazzs = ClassOperateHelper.findClassInTargetPackage(packageName);

        if (clazzs.length == 0) {
            return null;
        }
        List<Class<? extends Annotation>> l = new LinkedList<>();
        for (Class c : clazzs) {
            if (c.isAnnotationPresent(targetMarkAnnotation)) {
                l.add(c);
            }
        }
        return l.toArray(new Class[l.size()]);
    }

    /**
     * This method is used to parse fields in corresponding class that the fields are marked by
     * annotation that is marked by toMarkAnnotation.
     *
     * @param targetClass      the target class.
     * @param toMarkAnnotation the annotation that marks the target fields' annotation, means this parameter is
     *                         used to mark annotation that marks corresponding field.
     * @return all the fields what have marked by some annotations that those annotations are marked by toMarkAnnotation.
     */
    public static Field[] parseFieldsHaveAnnotation(Class targetClass, Class<? extends Annotation> toMarkAnnotation) throws IOException, ClassNotFoundException {
        Field[] allFieldsInTargetClass = ClassOperateHelper.retrieveFieldsBelongProperty(targetClass);
        Class<? extends Annotation>[] markedAnnotations = parseMarkAnnotation(toMarkAnnotation);
        List<Field> result = new ArrayList<>();

        for (Field oneField : allFieldsInTargetClass) {
            for (Class<? extends Annotation> markedAnnotation : markedAnnotations) {
                if (oneField.isAnnotationPresent(markedAnnotation)) {
                    result.add(oneField);
                    break;
                }
            }
        }

        return result.toArray(new Field[result.size()]);
    }

    public static boolean checkRelationalAnnotationForField(Field field) {
        Class[] targetClasses;
        try {
            targetClasses = AnnotationHelper.parseMarkAnnotation(RelationalMarkAnnotation.class);
        } catch (IOException | ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
        boolean[] resultBool = new boolean[targetClasses.length];
        for (int i = 0; i < targetClasses.length; i++) {
            if (field.isAnnotationPresent(targetClasses[i])) {
                resultBool[i] = true;
            }
        }
        return checkForBoolResult(resultBool);
    }

    private static boolean checkForBoolResult(boolean[] boolResult) {
        boolean flag = false;
        for (boolean b : boolResult) {
            if (b) {
                if (flag) {
                    throw new TooManyAnnotationException("字段上的关系注解只能有一个");
                }
                flag = true;
            }
        }

        return flag;
    }
}
